///\file model.cpp
///\brief contient les implémentations des méthodes spécifiées dans model.hh
///\author Allan BLANCHARD
#include <fstream>
#include <cstdlib>
#include <cmath>
#include <limits>

#include <GL/glew.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include "model.hh"

#define BUFFER_OFFSET(a) ((char*)NULL + (a))

unsigned int Model::totalsommets = 0;
unsigned int Model::totalfaces = 0;

void VectNormal(float* a, float* b, float* c, float *vn)
{ 
  vn[0] = (b[1]-a[1])*(b[2]-a[2])-(b[2]-a[2])*(b[1]-a[1]);
  vn[1] = (b[2]-a[2])*(c[0]-a[0])-(b[0]-a[0])*(c[2]-a[2]);
  vn[2] = (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0]);

  float norme = sqrt(vn[0]*vn[0]+vn[1]*vn[1]+vn[2]*vn[2]);
  vn[0] /= norme;
  vn[1] /= norme;
  vn[2] /= norme;
}

Model::Model(const char* name, bool transparent): 
  texture(0), transparent(transparent), vbo(false)
{
  std::ifstream ifs(name, std::ios::in);
  if(!ifs) exit(1);
  
  std::string head;
  ifs>>head;
  
  bool tex = false;
  tex = (head == "KPO");
  if(!tex && head != "OFF") exit(1);
  
  int fake;

  ifs>>nv;
  ifs>>ni;
  ifs>>fake;

  totalsommets += nv;
  totalfaces += ni;

  if(tex) ncoords = 8;
  else    ncoords = 6;
  
  indices = new unsigned int[ni*3];
  coords = new float[nv*ncoords];

  zmin = ymin = xmin = +std::numeric_limits<float>::max();
  zmax = ymax = xmax = -std::numeric_limits<float>::max();

  for(unsigned int i(0); i < nv; i++){
    ifs>>coords[i*ncoords]>>coords[i*ncoords+1]>>coords[i*ncoords+2];
    
    if(coords[i*ncoords] < xmin)   xmin = coords[i*ncoords];
    if(coords[i*ncoords+1] < ymin) ymin = coords[i*ncoords+1];
    if(coords[i*ncoords+2] < zmin) zmin = coords[i*ncoords+2];
    if(coords[i*ncoords] > xmax)   xmax = coords[i*ncoords];
    if(coords[i*ncoords+1] > ymax) ymax = coords[i*ncoords+1];
    if(coords[i*ncoords+2] > zmax) zmax = coords[i*ncoords+2];
  }
  
  boxify();
  center();
  
  if(tex){
    head = name;
    head += ".ppm";
    loadtex(head.c_str());
    glGenTextures(1, &texid);

    for(unsigned int i(0); i < nv; i++)
      ifs>>coords[i*ncoords+6]>>coords[i*ncoords+7];

    glBindTexture(GL_TEXTURE_2D, texid);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texw, texh, 
		 0, GL_RGB, GL_UNSIGNED_BYTE, texture);
  }

  for(unsigned int i(0); i < ni; i++){
    int n;
    ifs>>n;
    if(n != 3) return;
    ifs>>indices[i*3]>>indices[i*3+1]>>indices[i*3+2];
  }  
  computenorms();
  
  amb[0] = amb[1] = amb[2] = .2;
  dif[0] = dif[1] = dif[2] = .8;
  spe[0] = spe[1] = spe[2] = 0.;
  emi[0] = emi[1] = emi[2] = 0.;
  
  amb[3] = dif[3] = spe[3] = emi[3] = 1.;
  shi = 128;

  ifs.close();
}

Model::~Model(){
  free();
}

void Model::free(){
  if(texture)
    glDeleteTextures(1, &texid);
  glDeleteBuffers(1, &buffer);
  glDeleteBuffers(1, &index_buffer);

  if(indices)   delete[] indices;
  if(texture)   delete[] texture;
  
  delete[] coords;
}

void Model::loadtex(const char* name){
  std::ifstream ifs(name, std::ios::in);
  std::string head;
  getline(ifs,head);
  
  if (head != "P6") exit(1);
  
  getline(ifs, head);
  
  int bidon;
  ifs>>texw>>texh>>bidon;

  texture = new GLubyte[texw*texh*3];
  
  bidon = static_cast<int>(ifs.get());

  ifs.read(reinterpret_cast<char*>(texture), texw*texh*3);
  ifs.close();
}

void Model::boxify(){
  float maxdim = xmax-xmin;
  if(ymax-ymin > maxdim) maxdim = ymax-ymin;
  if(zmax-zmin > maxdim) maxdim = zmax-zmin;

  for(unsigned int i(0); i < nv; i++){
    coords[i*ncoords] /= maxdim;
    coords[i*ncoords+1] /= maxdim;
    coords[i*ncoords+2] /= maxdim;
  }
  xmin /= maxdim;
  xmax /= maxdim;
  ymin /= maxdim;
  ymax /= maxdim;
  zmin /= maxdim;
  zmax /= maxdim;
}

void Model::center(){
  float moyx((xmin+xmax)/2);
  float moyy((ymin+ymax)/2);
  float moyz((zmin+zmax)/2);

  for(unsigned int i(0); i < nv; i++){
    coords[i*ncoords] -= moyx;
    coords[i*ncoords+1] -= moyy;
    coords[i*ncoords+2] -= moyz;
  }

  xmin -= moyx;
  xmax -= moyx;
  ymin -= moyy;
  ymax -= moyy;
  zmin -= moyz;
  zmax -= moyz;
}

void Model::computenorms(){
  for(unsigned int i(0); i < nv; i++){
    coords[i*ncoords+3] = 0;
    coords[i*ncoords+4] = 0;
    coords[i*ncoords+5] = 0;
  }
  
  for(unsigned int i(0); i < ni; i++){
    float normale[3];

    unsigned int s1(indices[i*3]);
    unsigned int s2(indices[i*3+1]);
    unsigned int s3(indices[i*3+2]);

    VectNormal(&coords[s1*ncoords],
	       &coords[s2*ncoords],
	       &coords[s3*ncoords],
	       normale);
      
    for(unsigned int k(0); k < 3; k++){
      coords[s1*ncoords+3+k] += normale[k];
      coords[s2*ncoords+3+k] += normale[k];
      coords[s3*ncoords+3+k] += normale[k];
    }
  }    
  for(unsigned int i(0); i < nv; i++){
    float *vn(&coords[i*ncoords+3]);
    float norme(sqrt(vn[0]*vn[0]+vn[1]*vn[1]+vn[2]*vn[2]));
    vn[0] /= norme;
    vn[1] /= norme;
    vn[2] /= norme;
  }
}

void Model::draw() const{
  if(transparent){
    glDisable(GL_LIGHTING);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    
    glColor4f(amb[0], amb[1], amb[2], amb[3]);
    
    if(!vbo){
      glVertexPointer(3, GL_FLOAT, ncoords*sizeof(float), &coords[0]);
      glNormalPointer(GL_FLOAT, ncoords*sizeof(float), &coords[3]);
      if(texture)
	glTexCoordPointer(2, GL_FLOAT, ncoords*sizeof(float), &coords[6]);
      glIndexPointer(GL_UNSIGNED_INT, 0, indices);
      
      glDrawElements(GL_TRIANGLES, ni*3, GL_UNSIGNED_INT, indices);
    }
    else{
      glBindBuffer(GL_ARRAY_BUFFER, buffer);
      
      glVertexPointer(3, GL_FLOAT, ncoords*sizeof(float), BUFFER_OFFSET(0));
      glNormalPointer(GL_FLOAT, ncoords*sizeof(float), BUFFER_OFFSET(3));
      
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
      glIndexPointer(GL_UNSIGNED_INT, 0, BUFFER_OFFSET(0));
      
      glDrawElements(GL_TRIANGLES, ni*3, GL_UNSIGNED_INT, BUFFER_OFFSET(0));
      
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }
    glEnable(GL_LIGHTING);
  }

  glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,amb);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,dif);
  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,spe);
  glMaterialfv(GL_FRONT_AND_BACK,GL_EMISSION,emi);
  glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,&shi);

  if(texture){
    glEnable(GL_TEXTURE_2D);
    glBindTexture (GL_TEXTURE_2D, texid); 
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  }    
  
  
  if(!vbo){
    glVertexPointer(3, GL_FLOAT, ncoords*sizeof(float), &coords[0]);
    glNormalPointer(GL_FLOAT, ncoords*sizeof(float), &coords[3]);
    if(texture)
      glTexCoordPointer(2, GL_FLOAT, ncoords*sizeof(float), &coords[6]);
    glIndexPointer(GL_UNSIGNED_INT, 0, indices);
    
    glDrawElements(GL_TRIANGLES, ni*3, GL_UNSIGNED_INT, indices);
  }
  else{
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    
    glVertexPointer(3, GL_FLOAT, ncoords*sizeof(float), BUFFER_OFFSET(0));
    glNormalPointer(GL_FLOAT, ncoords*sizeof(float), BUFFER_OFFSET(12));
    
    if(texture)
      glTexCoordPointer(2, GL_FLOAT, ncoords*sizeof(float), BUFFER_OFFSET(24));
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
    glIndexPointer(GL_UNSIGNED_INT, 0, BUFFER_OFFSET(0));
    
    glDrawElements(GL_TRIANGLES, ni*3, GL_UNSIGNED_INT, BUFFER_OFFSET(0));
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  }
  if(texture)
    glDisable(GL_TEXTURE_2D);
  if(transparent)
    glDisable(GL_BLEND);
}

void Model::setAmbient(float r, float g, float b, float a){
  amb[0] = r;
  amb[1] = g;
  amb[2] = b;
  amb[3] = a;
}

void Model::setDiffuse(float r, float g, float b, float a){
  dif[0] = r;
  dif[1] = g;
  dif[2] = b;
  dif[3] = a;
}

void Model::setSpecular(float r, float g, float b, float a){
  spe[0] = r;
  spe[1] = g;
  spe[2] = b;
  spe[3] = a;
}

void Model::setEmission(float r, float g, float b, float a){
  emi[0] = r;
  emi[1] = g;
  emi[2] = b;
  emi[3] = a;
}

void Model::setShininess(float val){
  shi = val;
}

void Model::resize(float factor){
  for(unsigned int i = 0; i < nv; i++){
    coords[i*ncoords] *= factor;
    coords[i*ncoords+1] *= factor;
    coords[i*ncoords+2] *= factor;
  }

  xmin*=factor;
  xmax*=factor;
  ymin*=factor;
  ymax*=factor;
  zmin*=factor;
  zmax*=factor;
}

void Model::toVBO(){
  if(!vbo){
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);

    int buffercoordsize = ncoords*nv*sizeof(float);
    
    glBufferData(GL_ARRAY_BUFFER, buffercoordsize, 0, GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, buffercoordsize, coords);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    glGenBuffers(1, &index_buffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
		 ni*3*sizeof(unsigned int), 
		 0, 
		 GL_STATIC_DRAW);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 
		    0, 
		    ni*3*sizeof(unsigned int), 
		    indices);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  }
  vbo = true;
}
